package com.ikas.ai.server.module.data.service;

import cn.afterturn.easypoi.excel.ExcelExportUtil;
import cn.afterturn.easypoi.excel.ExcelImportUtil;
import cn.afterturn.easypoi.excel.entity.ExportParams;
import cn.afterturn.easypoi.excel.entity.ImportParams;
import cn.afterturn.easypoi.excel.entity.enmus.ExcelType;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ikas.ai.consts.Consts;
import com.ikas.ai.enums.redis.RedisKeyEnum;
import com.ikas.ai.server.error.BusinessException;
import com.ikas.ai.server.module.InitRedis;
import com.ikas.ai.server.module.data.dao.TripMeteDao;
import com.ikas.ai.server.module.data.model.TripMete;
import com.ikas.ai.server.module.data.model.dto.TripMeteAddDTO;
import com.ikas.ai.server.module.data.model.dto.TripMeteExcelDTO;
import com.ikas.ai.server.module.data.model.dto.TripMeteExportDTO;
import com.ikas.ai.server.module.data.model.dto.TripMeteUpdateDTO;
import com.ikas.ai.utils.RedisUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.ss.usermodel.Workbook;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * @Description: mete测点服务类
 * @Author: qi.yilai
 * @Date: 2023/2/2 17:36
 * @Version: 1.0.0
 **/
@Service
@Slf4j
public class TripMeteService extends ServiceImpl<TripMeteDao, TripMete> {

    @Autowired
    private TripMeteDao meteDao;

    @Autowired
    private InitRedis initRedis;

    /**
     * 根据设备编号获取设备列表
     *
     * @param equipmentCode
     * @return
     */
    public List<TripMete> getMetesByEquipmentCode(String equipmentCode) {
        LambdaQueryWrapper<TripMete> meteLambdaQueryWrapper = new LambdaQueryWrapper<>();
        meteLambdaQueryWrapper.eq(TripMete::getEquipmentCode, equipmentCode);
        return list(meteLambdaQueryWrapper);
    }

    /**
     * 查询测点
     *
     * @param meteCode
     * @return
     */
    public List<TripMete> queryMetes(String meteCode, String tripType, String meteId, String signalCnName, String machineNo, Long oid, String parentNodeName, String nodeName, String equipmentCode) {
        if (StringUtils.isNotBlank(meteId)) {
            Pattern compile = Pattern.compile(Consts.REGEX);
            if (!compile.matcher(meteId).matches()) {
                throw new BusinessException("测点id必须为数字");
            }
        }
        LambdaQueryChainWrapper<TripMete> lambdaQueryWrapper = new LambdaQueryChainWrapper<>(meteDao);
        lambdaQueryWrapper.like(StringUtils.isNotBlank(meteCode), TripMete::getMeteCode, meteCode);
        lambdaQueryWrapper.eq(StringUtils.isNotBlank(meteId), TripMete::getMeteId, StringUtils.isNotBlank(meteId) ? Long.valueOf(meteId) : null);
        lambdaQueryWrapper.eq(Objects.nonNull(oid), TripMete::getOid, oid);
        lambdaQueryWrapper.eq(StringUtils.isNotBlank(tripType), TripMete::getTripType, tripType);
        lambdaQueryWrapper.like(StringUtils.isNotBlank(signalCnName), TripMete::getSignalCnName, signalCnName);
        lambdaQueryWrapper.eq(StringUtils.isNotBlank(machineNo), TripMete::getMachineNo, machineNo);
        lambdaQueryWrapper.like(StringUtils.isNotBlank(equipmentCode), TripMete::getEquipmentCode, equipmentCode);
        lambdaQueryWrapper.like(StringUtils.isNotBlank(parentNodeName), TripMete::getParentNodeName, parentNodeName);
        lambdaQueryWrapper.like(StringUtils.isNotBlank(nodeName), TripMete::getNodeName, nodeName);
        lambdaQueryWrapper.orderByDesc(TripMete::getCreateTime);
        return lambdaQueryWrapper.list();
    }

    @Transactional(rollbackFor = Exception.class)
    public TripMeteAddDTO add(TripMeteAddDTO meteAddDTO, Long userId) {
        List<TripMete> metes = new ArrayList<>(selectByMeteName(meteAddDTO.getMeteCode()));
        if (CollectionUtils.isNotEmpty(metes)) {
            throw new BusinessException("该变量名称已存在！");
        }
        List<TripMete> meteIdMetes = selectByMeteId(meteAddDTO.getMeteId());
        if (CollectionUtils.isNotEmpty(meteIdMetes)) {
            throw new BusinessException("该测点Id已存在！");
        }
        List<TripMete> oidMetes = selectByOid(meteAddDTO.getOid());
        if (CollectionUtils.isNotEmpty(oidMetes)) {
            throw new BusinessException("该oid已存在！");
        }
        TripMete mete = new TripMete();
        BeanUtils.copyProperties(meteAddDTO, mete);
        mete.setCreateTime(new Date());
        mete.setUpdateTime(new Date());
        mete.setCreator(userId);
        mete.setUpdater(userId);
        save(mete);
        //更新redis
        initRedis.initTripMeteCache();
        return meteAddDTO;
    }

    private List<TripMete> selectByOid(Long oid) {
        LambdaQueryChainWrapper<TripMete> lambdaQueryChainWrapper = new LambdaQueryChainWrapper<>(meteDao);
        return lambdaQueryChainWrapper.eq(TripMete::getOid, oid).list();
    }

    @Transactional(rollbackFor = Exception.class)
    public TripMeteUpdateDTO update(TripMeteUpdateDTO meteUpdateDTO, Long userId) {
        TripMete source = meteDao.selectById(meteUpdateDTO.getId());
        if (Objects.isNull(source)) {
            throw new BusinessException("该变量不存在！");
        }
        List<TripMete> metes = new ArrayList<>(selectByMeteName(meteUpdateDTO.getMeteCode()));
        if (CollectionUtils.isNotEmpty(metes) && !source.getId().equals(metes.get(0).getId())) {
            throw new BusinessException("该变量名称已存在！");
        }

        List<TripMete> meteIdMetes = selectByMeteId(meteUpdateDTO.getMeteId());
        if (CollectionUtils.isNotEmpty(meteIdMetes) && !source.getId().equals(meteIdMetes.get(0).getId())) {
            throw new BusinessException("该测点Id已存在！");
        }
        List<TripMete> oidMetes = selectByOid(meteUpdateDTO.getOid());
        if (CollectionUtils.isNotEmpty(oidMetes) && !source.getId().equals(oidMetes.get(0).getId())) {
            throw new BusinessException("该oid已存在！");
        }
        BeanUtils.copyProperties(meteUpdateDTO, source);
        source.setUpdateTime(new Date());
        source.setUpdater(userId);
        updateById(source);
        initRedis.initTripMeteCache();
        return meteUpdateDTO;
    }

    private List<TripMete> selectByMeteId(Long meteId) {
        LambdaQueryChainWrapper<TripMete> wrapper = new LambdaQueryChainWrapper<>(meteDao);
        return wrapper.eq(TripMete::getMeteId, meteId).list();
    }

    private void resetTripMeteRedisCache() {
        //更新redis
        RedisUtils.del(RedisKeyEnum.ALL_TRIP_METE_DATA.key());
        RedisUtils.List.add(RedisKeyEnum.ALL_TRIP_METE_DATA.key(), this.list().stream().map(JSONObject::toJSONString).collect(Collectors.toList()));
    }

    public Set<TripMete> selectByMeteName(String meteName) {
        LambdaQueryChainWrapper<TripMete> lambdaQueryChainWrapper = new LambdaQueryChainWrapper<>(meteDao);
        return lambdaQueryChainWrapper.eq(TripMete::getMeteCode, meteName).list().stream().filter(distinctByKey(TripMete::getMeteCode)).collect(Collectors.toSet());
    }

    /**
     * 通过excel批量导入测点
     *
     * @param file
     * @param userId
     */
    public void importMetes(MultipartFile file, Long userId) throws Exception {
        ImportParams importParams = new ImportParams();
        //设置从第几个（索引）sheet开始
        importParams.setStartSheetIndex(9);
        List<TripMeteExcelDTO> dtoList = ExcelImportUtil.importExcel(file.getInputStream(), TripMeteExcelDTO.class, importParams);
        log.info("导入的测点数据:{}", JSON.toJSONString(dtoList));
        //根据meteName去重
        dtoList = dtoList.stream().filter(i -> Objects.nonNull(i.getMeteCode())).filter(distinctByKey(TripMeteExcelDTO::getMeteCode)).collect(Collectors.toList());
        LambdaQueryChainWrapper<TripMete> lambdaQueryChainWrapper = new LambdaQueryChainWrapper<>(meteDao);
        List<TripMete> all = lambdaQueryChainWrapper.list();
        Map<String, TripMete> meteMap = all.stream().collect(Collectors.toMap(TripMete::getMeteCode, i -> i));
        //将测点分为更新和保存两组
        Map<Boolean, List<TripMeteExcelDTO>> map = dtoList.stream().collect(Collectors.partitioningBy(i -> meteMap.containsKey(i.getMeteCode())));
        //更新的测点集合
        List<TripMeteExcelDTO> updateList = map.get(true);
        //保存的测点集合
        List<TripMeteExcelDTO> saveList = map.get(false);

        List<TripMete> saves = saveList.stream().map(meteExcelDTO -> {
            TripMete mete = new TripMete();
            BeanUtils.copyProperties(meteExcelDTO, mete);
            mete.setCreator(userId);
            mete.setUpdater(userId);
            mete.setCreateTime(new Date());
            mete.setUpdateTime(new Date());
            return mete;
        }).collect(Collectors.toList());
        saveBatch(saves);

        List<TripMete> updates = updateList.stream().map(meteExcelDTO -> {
            TripMete mete = meteMap.get(meteExcelDTO.getMeteCode());
            BeanUtils.copyProperties(meteExcelDTO, mete);
            mete.setUpdater(userId);
            mete.setUpdateTime(new Date());
            return mete;
        }).collect(Collectors.toList());
        updateBatchById(updates);
        initRedis.initBaseMeteCache();
    }

    private static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
        Map<Object, Boolean> seen = new ConcurrentHashMap<>();
        return t -> seen.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
    }

    @Transactional(rollbackFor = Exception.class)
    public void importTripMete(MultipartFile file, Long userId) throws Exception {
        ImportParams importParams = new ImportParams();
        List<TripMeteExportDTO> dtoList = ExcelImportUtil.importExcel(file.getInputStream(), TripMeteExportDTO.class, importParams);
        log.info("导入的跳机测点数据:{}", JSON.toJSONString(dtoList));
//        if (CollectionUtils.isNotEmpty(dtoList)) {
//            //导入数据不为空，先删除原始数据
//            List<TripMete> tripMeteList = meteDao.selectList(null);
//            meteDao.deleteBatchIds(tripMeteList.stream().map(TripMete::getId).collect(Collectors.toList()));
//        }
        //根据meteName去重
        dtoList = dtoList.stream().filter(i -> Objects.nonNull(i.getMeteCode())).filter(distinctByKey(TripMeteExportDTO::getMeteCode)).collect(Collectors.toList());
        LambdaQueryChainWrapper<TripMete> lambdaQueryChainWrapper = new LambdaQueryChainWrapper<>(meteDao);
        List<TripMete> all = lambdaQueryChainWrapper.list();
//        Map<String, TripMete> meteMap = all.stream().collect(Collectors.toMap(TripMete::getMeteCode, i -> i));
        Map<String, List<TripMete>> meteMap = all.stream().collect(Collectors.groupingBy(TripMete::getMeteCode));
        //将测点分为更新和保存两组
        Map<Boolean, List<TripMeteExportDTO>> map = dtoList.stream().collect(Collectors.partitioningBy(i -> meteMap.containsKey(i.getMeteCode())));
        //更新的测点集合
        List<TripMeteExportDTO> updateList = map.get(true);
        //保存的测点集合
        List<TripMeteExportDTO> saveList = map.get(false);

        List<TripMete> saves = saveList.stream().map(meteExcelDTO -> {
            TripMete mete = new TripMete();
            BeanUtils.copyProperties(meteExcelDTO, mete);
            mete.setCreator(userId);
            mete.setUpdater(userId);
            mete.setCreateTime(new Date());
            mete.setUpdateTime(new Date());
            return mete;
        }).collect(Collectors.toList());
        saveBatch(saves);

        List<TripMete> updates = new ArrayList<>();
        updateList.forEach(meteExcelDTO -> {
            List<TripMete> metes = meteMap.get(meteExcelDTO.getMeteCode());
            metes.forEach(mete -> {
                BeanUtils.copyProperties(meteExcelDTO, mete);
                mete.setUpdater(userId);
                mete.setUpdateTime(new Date());
            });
            updates.addAll(metes);
        });
        updateBatchById(updates);
        initRedis.initTripMeteCache();
    }

    public Workbook exportTripMete(String meteCode, String tripType, Long meteId, String signalCnName, String machineNo, Long oid, String parentNodeName, String nodeName, String equipmentCode, Long userId) {
        List<TripMete> tripMeteList = queryMetes(meteCode, tripType, Objects.nonNull(meteId) ? String.valueOf(meteId) : null, signalCnName, machineNo, oid, parentNodeName, nodeName, equipmentCode);
        if (CollectionUtils.isEmpty(tripMeteList)) {
            throw new BusinessException("导出数据为空！");
        }
        List<TripMeteExportDTO> excelDTOList = tripMeteList.stream().map(item -> {
            TripMeteExportDTO tripMeteExcelDTO = new TripMeteExportDTO();
            BeanUtils.copyProperties(item, tripMeteExcelDTO);
            return tripMeteExcelDTO;
        }).collect(Collectors.toList());
        return ExcelExportUtil.exportExcel(new ExportParams(null, "跳机测点", ExcelType.XSSF), TripMeteExportDTO.class, excelDTOList);
    }

    @Transactional(rollbackFor = Exception.class)
    public void delete(Long id) {
        removeById(id);
        initRedis.initTripMeteCache();
    }

    @Transactional(rollbackFor = Exception.class)
    public void deleteByIds(List<Long> ids) {
        removeByIds(ids);
        initRedis.initTripMeteCache();
    }
}
